﻿using Xejen.Communication.Configuration;
using Xejen.Communication.Socket.Abstract;
using Xejen.Communication.Socket.Configuration;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace Xejen.Communication.Socket
{
    /// <summary>
    /// 使用<see langword="SocketAsyncEventArgs"/>技术向远程服务器发起请求
    /// </summary>
    /// <creator>marc</creator>
    internal class AsyncInvoker : ISocketInvoker
    {
        /// <inheritdoc/>
        private ConnectionConfig _config { get; set; }

        private bool _disposed;
        private System.Net.Sockets.Socket _clientSocket;

        /// <inheritdoc cref="AsyncInvoker"/>
        protected AsyncInvoker() { }

        /// <inheritdoc cref="AsyncInvoker"/>
        public AsyncInvoker(ConnectionConfig config)
        {
            Check.NotNull(config, nameof(config));

            _config = config;

            if (config.ConnectMode == ConnectMode.Long)
            {
                _clientSocket = CreateSocket();
            }
        }

        /// <summary>
        /// 创建一个新的套接字
        /// </summary>
        /// <returns></returns>
        private System.Net.Sockets.Socket CreateSocket()
        {
            var clientSocket = new System.Net.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            clientSocket.SendBufferSize = _config.SendBufferSize;
            clientSocket.ReceiveBufferSize = _config.ReceiveBufferSize;
            clientSocket.SendTimeout = _config.SendTimeout;
            clientSocket.ReceiveTimeout = _config.ReceiveTimeout;

            return clientSocket;
        }

        /// <inheritdoc/>
        public async Task<SocketResponse> SendAsync(byte[] input, int timeout = -1, CancellationToken cancellationToken = default)
        {
            return await InnertSendAsync(input, timeout, cancellationToken);
        }

        private async Task<SocketResponse> InnertSendAsync(byte[] input, int timeout = -1, CancellationToken cancellationToken = default)
        {
            _clientSocket = this._clientSocket ?? CreateSocket();

            var connectTask = _clientSocket.ConnectAsync(new IPEndPoint(IPAddress.Parse(_config.ServerAddress), _config.ServerPort));

            var completedTask = await Task.WhenAny(connectTask, Task.Delay(timeout, cancellationToken));

            if (completedTask != connectTask)
            {
                throw new TimeoutException($"Socket连接超时，超时时间限定：{timeout} 毫秒");
            }

            await connectTask;

            byte[] dataBytes = input;// Config.Encoding.GetBytes(JsonSerializer.Serialize(input, Config.JsonSerializerOptions));
            await _clientSocket.SendAsync(dataBytes, SocketFlags.None);

            using (MemoryStream memoryStream = new MemoryStream())
            {
                int bytesRead;
                byte[] buffer = new byte[4096];

                do
                {
                    bytesRead = await _clientSocket.ReceiveAsync(buffer, SocketFlags.None);
                    memoryStream.Write(buffer, 0, bytesRead);
                } while (bytesRead == buffer.Length);

                byte[] datas = memoryStream.ToArray();
                string responseData = _config.Encoding.GetString(datas);
                //Debug.WriteLine($"Socket返回的结果：{responseData}");

                SocketResponse result = SocketResponse.Success(datas, encoding: _config.Encoding);

                if (_config.ConnectMode == ConnectMode.Short)
                {
                    Close();
                }

                return result;
            }
        }

        /// <inheritdoc/>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Close()
        {
            _clientSocket?.Close();
            _clientSocket?.Dispose();
            _clientSocket = null;
        }

        /// <summary>
        /// 释放资源
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
            {
                return;
            }

            if (disposing)
            {
                Close();
            }

            _disposed = true;
        }
    }
}
